﻿namespace ClearLines.TspDemo.CSharp
{
   using System;
   using System.Collections.Generic;
   using System.Linq;
   using System.Windows;
   using System.Windows.Media;
   using Bumblebee;
   using GalaSoft.MvvmLight;
   using GalaSoft.MvvmLight.Command;
   using GalaSoft.MvvmLight.Threading;
   using Random = System.Random;

   public class RouteViewModel : ViewModelBase
   {
      private const double Height = 545d;
      private const double Width = 545d;

      private readonly Solver<IList<City>> solver;
      private readonly RelayCommand run;
      private int citiesCount = 100;
      private PointCollection points;
      private double quality;
      private DateTime discoveryTime;
      private bool isSearching;

      public RouteViewModel()
      {
         var solver = new Solver<IList<City>>();
         solver.FoundSolution += SolutionFound;
         this.solver = solver;
         DispatcherHelper.Initialize();

         this.run = new RelayCommand(this.RunExecute);
      }

      public PointCollection Points
      {
         get { return this.points; }
         private set
         {
            this.points = value;
            this.RaisePropertyChanged("Points");
         }
      }

      public int CitiesCount
      {
         get { return this.citiesCount; }
         set
         {
            if (value != this.citiesCount)
            {
               this.citiesCount = value;
               this.RaisePropertyChanged("CitiesCount");
            }
         }
      }

      public bool IsSearching
      {
         get { return this.isSearching; }
         private set
         {
            if (this.isSearching != value)
            {
               this.isSearching = value;
               this.RaisePropertyChanged("IsSearching");
               this.RaisePropertyChanged("ButtonLabel");
            }
         }
      }

      public double Quality
      {
         get { return this.quality; }
         private set
         {
            if (this.quality != value)
            {
               this.quality = value;
               this.RaisePropertyChanged("Quality");
            }
         }
      }

      public DateTime DiscoveryTime
      {
         get { return this.discoveryTime; }
         private set
         {
            if (this.discoveryTime != value)
            {
               this.discoveryTime = value;
               this.RaisePropertyChanged("DiscoveryTime");
            }
         }
      }

      public RelayCommand Run
      {
         get { return this.run; }
      }

      public string ButtonLabel
      {
         get
         {
            if (this.IsSearching)
            {
               return "Stop";
            }

            return "Search";
         }
      }

      private void RunExecute()
      {
         if (this.IsSearching)
         {
            this.StopSearch();
         }
         else
         {
            var cities = new List<City>();
            var rng = new Random();
            for (var i = 0; i < this.CitiesCount; i++)
            {
               var x = Width * rng.NextDouble();
               var y = Height * rng.NextDouble();
               var city = new City(i.ToString(), x, y);
               cities.Add(city);
            }

            this.StartSearch(cities);
         }
      }

      private void StartSearch(IList<City> cities)
      {
         var generator = new Func<Random, IList<City>>(
            (random) => Tsp.Shuffle(random, cities));

         var mutator = new Func<IList<City>, Random, IList<City>>(
            (solution, random) => Tsp.Swap(random, solution));

         var evaluator = new Func<IList<City>, double>(
            (circuit) => -Tsp.Length(circuit));

         var problem = new Problem<IList<City>>(generator, mutator, evaluator);

         this.solver.Search(problem);
         this.IsSearching = true;
      }

      private void StopSearch()
      {
         this.solver.Stop();
         this.IsSearching = false;
      }

      private void SolutionFound(object sender, SolutionMessage<IList<City>> args)
      {
         DispatcherHelper.CheckBeginInvokeOnUI(
            () =>
            {
               var points = args.Solution.Select(city => new Point(city.X, city.Y));
               this.Points = new PointCollection(points);
               this.Quality = args.Quality;
               this.DiscoveryTime = args.DateTime;
            });
      }
   }
}